# pdb & cProfile:调试和性能分æžçš„æ³•å® åœ¨å®žé™…ç”Ÿäº§çŽ¯å¢ƒä¸ï¼Œå¯¹ä»£ç 进行调试和性能分æžï¼Œæ˜¯ä¸€ä¸ªæ°¸è¿œéƒ½é€ƒä¸å¼€çš„è¯é¢˜ã€‚调试和性能分æžçš„主è¦åœºæ™¯ï¼Œé€šå¸¸æœ‰è¿™ä¹ˆä¸‰ä¸ªï¼š + 一是代ç 本身有问题,需è¦æˆ‘们找到 root cause 并修å¤ï¼› + 二是代ç 效率有问题,比如过度浪费资æºï¼Œå¢žåŠ latencyï¼Œå› æ¤éœ€è¦æˆ‘们 debugï¼› + 三是在开å‘æ–°çš„ feature 时,一般都需è¦æµ‹è¯•ã€‚ 在é‡åˆ°è¿™äº›åœºæ™¯æ—¶ï¼Œç©¶ç«Ÿåº”该使用哪些工具,如何æ£ç¡®çš„使用这些工具,应该éµå¾ªä»€ä¹ˆæ ·çš„æ¥éª¤ç‰ç‰ï¼Œå°±æ˜¯è¿™èŠ‚课我们è¦è®¨è®ºçš„è¯é¢˜ã€‚ ## 用 pdb 进行代ç 调试 ### pdb çš„å¿…è¦æ€§ 首先,我们æ¥çœ‹ä»£ç 的调试。也许ä¸å°‘人会有疑问:代ç 调试?说白了ä¸å°±æ˜¯åœ¨ç¨‹åºä¸ä½¿ç”¨ print() è¯å¥å—? 没错,在程åºä¸ç›¸åº”的地方打å°ï¼Œçš„确是调试程åºçš„一个常用手段,但这åªé€‚用于å°åž‹ç¨‹åºã€‚å› ä¸ºä½ æ¯æ¬¡éƒ½å¾—é‡æ–°è¿è¡Œæ•´ä¸ªç¨‹åºï¼Œæˆ–是一个完整的功能模å—,æ‰èƒ½çœ‹åˆ°æ‰“å°å‡ºæ¥çš„å˜é‡å€¼ã€‚如果程åºä¸å¤§ï¼Œæ¯æ¬¡è¿è¡Œéƒ½éžå¸¸å¿«ï¼Œé‚£ä¹ˆä½¿ç”¨ print(),的确是很方便的。 但是,如果我们é¢å¯¹çš„是大型程åºï¼Œè¿è¡Œä¸€æ¬¡çš„调试æˆæœ¬å¾ˆé«˜ã€‚特别是对于一些 tricky 的例åæ¥è¯´ï¼Œå®ƒä»¬é€šå¸¸éœ€è¦åå¤è¿è¡Œè°ƒè¯•ã€è¿½æº¯ä¸Šä¸‹æ–‡ä»£ç ,æ‰èƒ½æ‰¾åˆ°é”™è¯¯æ ¹æºã€‚è¿™ç§æƒ…况下,仅仅ä¾èµ–打å°çš„效率自然就很低了。 我们å¯ä»¥æƒ³è±¡ä¸‹é¢è¿™ä¸ªåœºæ™¯ã€‚æ¯”å¦‚ä½ æœ€å¸¸ä½¿ç”¨çš„åŒ—äº¬å›¾çµå¦é™¢ App,最近出现了一个 bugï¼Œéƒ¨åˆ†ç”¨æˆ·æ— æ³•ç™»é™†ã€‚äºŽæ˜¯ï¼ŒåŽç«¯å·¥ç¨‹å¸ˆä»¬å¼€å§‹ debug。 他们怀疑错误的代ç 逻辑在æŸå‡ 个函数ä¸ï¼Œå¦‚果使用 print() è¯å¥ debug,很å¯èƒ½å‡ºçŽ°çš„场景是,工程师们在他们认为的 10 个最å¯èƒ½å‡ºçŽ° bug 的地方,都使用 print() è¯å¥ï¼Œç„¶åŽè¿è¡Œæ•´ä¸ªåŠŸèƒ½å—代ç (从å¯åŠ¨åˆ°è¿è¡ŒèŠ±äº† 5min),看打å°å‡ºæ¥çš„结果值,是ä¸æ˜¯å’Œé¢„期相符。 å¦‚æžœç»“æžœå€¼å’Œé¢„æœŸç›¸ç¬¦ï¼Œå¹¶èƒ½ç›´æŽ¥æ‰¾åˆ°é”™è¯¯æ ¹æºï¼Œæ˜¾ç„¶æ˜¯æœ€å¥½çš„。但实际情况往往是, + è¦ä¹ˆä¸Žé¢„期并ä¸ç›¸ç¬¦ï¼Œéœ€è¦é‡å¤ä»¥ä¸Šæ¥éª¤ï¼Œç»§ç» debugï¼› + è¦ä¹ˆè™½è¯´ä¸Žé¢„期相符,但å‰é¢çš„æ“作åªæ˜¯ç¼©å°äº†é”™è¯¯ä»£ç 的范围,所以ä»å¾—继ç»æ·»åŠ print() è¯å¥ï¼Œå†ä¸€æ¬¡è¿è¡Œç›¸åº”的代ç 模å—(åˆè¦ 5min),进行 debug。 ä½ å¯ä»¥çœ‹åˆ°ï¼Œè¿™æ ·çš„效率就很低下了。哪怕åªæ˜¯é‡åˆ°ç¨å¾®å¤æ‚一点的 case,两ã€ä¸‰ä¸ªå·¥ç¨‹å¸ˆä¸€ä¸‹åˆçš„时间å¯èƒ½å°±æ²¡äº†ã€‚ å¯èƒ½åˆæœ‰äººä¼šè¯´ï¼ŒçŽ°åœ¨å¾ˆå¤šçš„ IDE ä¸éƒ½æœ‰å†…置的 debug 工具å—? è¿™è¯è¯´çš„也没错。比如我们常用的 Pycharm,å¯ä»¥å¾ˆæ–¹ä¾¿åœ°åœ¨ç¨‹åºä¸è®¾ç½®æ–ç‚¹ã€‚è¿™æ ·ç¨‹åºåªè¦è¿è¡Œåˆ°æ–点处,便会自动åœä¸‹ï¼Œä½ å°±å¯ä»¥è½»æ¾æŸ¥çœ‹çŽ¯å¢ƒä¸å„个å˜é‡çš„值,并且å¯ä»¥æ‰§è¡Œç›¸åº”çš„è¯å¥ï¼Œå¤§å¤§æ高了调试的效率。 çœ‹åˆ°è¿™é‡Œï¼Œä½ ä¸ç¦ä¼šé—®ï¼Œæ—¢ç„¶é—®é¢˜éƒ½è§£å†³äº†ï¼Œé‚£ä¸ºä»€ä¹ˆè¿˜è¦å¦ä¹ pdb 呢?其实在很多大公å¸ï¼Œäº§å“çš„åˆ›é€ ä¸Žè¿ä»£ï¼Œå¾€å¾€éœ€è¦å¾ˆå¤šç¼–程è¯è¨€çš„支æŒï¼›å¹¶ä¸”,公å¸å†…部也会开å‘很多自己的接å£ï¼Œå°è¯•æŠŠå°½å¯èƒ½å¤šçš„è¯è¨€ç»™ç»“åˆèµ·æ¥ã€‚ 这就使得,很多情况下,å•ä¸€è¯è¨€çš„ IDE,对混åˆä»£ç 并ä¸æ”¯æŒ UI å½¢å¼çš„æ–点调试功能,或是åªå¯¹æŸäº›åŠŸèƒ½æ¨¡å—支æŒã€‚å¦å¤–,考虑到ä¸å°‘代ç å·²ç»æŒªåˆ°äº†ç±»ä¼¼ Jupyter çš„ Notebook ä¸ï¼Œå¾€å¾€å°±è¦æ±‚å¼€å‘者使用命令行的形å¼ï¼Œæ¥å¯¹ä»£ç 进行调试。 而 Python çš„ pdb,æ£æ˜¯å…¶è‡ªå¸¦çš„一个调试库。它为 Python 程åºæ供了交互å¼çš„æºä»£ç 调试功能,是命令行版本的 IDE æ–点调试器,完美地解决了我们刚刚讨论的这个问题。 ## 如何使用 pdb 了解了 pdb çš„é‡è¦æ€§ä¸Žå¿…è¦æ€§åŽï¼ŒæŽ¥ä¸‹æ¥ï¼Œæˆ‘们就一起æ¥çœ‹çœ‹ï¼Œpdb 在 Python ä¸åˆ°åº•åº”该如何使用。 首先,è¦å¯åŠ¨ pdb 调试,我们åªéœ€è¦åœ¨ç¨‹åºä¸ï¼ŒåŠ 入“import pdbâ€å’Œâ€œpdb.set_trace()â€è¿™ä¸¤è¡Œä»£ç 就行了,比如下é¢è¿™ä¸ªç®€å•çš„例å: ```python a = 1 b = 2 import pdb pdb.set_trace() c = 3 print(a + b + c) ``` 当我们è¿è¡Œè¿™ä¸ªç¨‹åºæ—¶æ—¶ï¼Œå®ƒçš„输出界é¢æ˜¯ä¸‹é¢è¿™æ ·çš„,表示程åºå·²ç»è¿è¡Œåˆ°äº†â€œpdb.set_trace()â€è¿™è¡Œï¼Œå¹¶ä¸”æš‚åœäº†ä¸‹æ¥ï¼Œç‰å¾…用户输入。 ```shell > /Users/yc/Desktop/code/demo.py(5)<module>() -> c = 3 (Pdb) ``` 这时,我们就å¯ä»¥æ‰§è¡Œï¼Œåœ¨ IDE æ–点调试器ä¸å¯ä»¥æ‰§è¡Œçš„一切æ“作,比如打å°ï¼Œè¯æ³•æ˜¯"p <expression>": ```shell (pdb) p a 1 (pdb) p b 2 ``` ä½ å¯ä»¥çœ‹åˆ°ï¼Œæˆ‘打å°çš„是 a å’Œ b 的值,分别为 1 å’Œ 2,与预期相符。为什么ä¸æ‰“å° c å‘¢ï¼Ÿæ˜¾ç„¶ï¼Œæ‰“å° c ä¼šæŠ›å‡ºå¼‚å¸¸ï¼Œå› ä¸ºç¨‹åºç›®å‰åªè¿è¡Œäº†å‰é¢å‡ 行,æ¤æ—¶çš„å˜é‡ c 还没有被定义: ```shell (pdb) p c *** NameError: name 'c' is not defined ``` 除了打å°ï¼Œå¸¸è§çš„æ“作还有“nâ€ï¼Œè¡¨ç¤ºç»§ç»æ‰§è¡Œä»£ç 到下一行,用法如下: ```shell (pdb) n -> print(a + b + c) ``` 而命令â€l“,则表示列举出当å‰ä»£ç 行上下的 11 è¡Œæºä»£ç ,方便开å‘者熟悉当å‰æ–点周围的代ç 状æ€ï¼š ```shell (pdb) l 1 a = 1 2 b = 2 3 import pdb 4 pdb.set_trace() 5 -> c = 3 6 print(a + b + c) ``` 命令“s“,就是 step into çš„æ„æ€ï¼Œå³è¿›å…¥ç›¸å¯¹åº”的代ç 内部。这时,命令行ä¸ä¼šæ˜¾ç¤ºâ€--Call--“的å—æ ·ï¼Œå½“ä½ æ‰§è¡Œå®Œå†…éƒ¨çš„ä»£ç å—åŽï¼Œå‘½ä»¤è¡Œä¸åˆ™ä¼šå‡ºçŽ°â€--Return--“的å—æ ·ã€‚ 我们æ¥çœ‹ä¸‹é¢è¿™ä¸ªä¾‹å: ```python def func(): print('enter func()') a = 1 b = 2 import pdb pdb.set_trace() func() c = 3 print(a + b + c) # pdb > /Users/yc/test.py(9)<module>() -> func() (pdb) s --Call-- > /Users/yc/test.py(1)func() -> def func(): (Pdb) l 1 -> def func(): 2 print('enter func()') 3 4 5 a = 1 6 b = 2 7 import pdb 8 pdb.set_trace() 9 func() 10 c = 3 11 print(a + b + c) (Pdb) n > /Users/yc/test.py(2)func() -> print('enter func()') (Pdb) n enter func() --Return-- > /Users/yc/test.py(2)func()->None -> print('enter func()') (Pdb) n > /Users/yc/test.py(10)<module>() -> c = 3 ``` 这里,我们使用命令â€s“进入了函数 func() 的内部,显示â€--Call--“;而当我们执行完函数 func() 内部è¯å¥å¹¶è·³å‡ºåŽï¼Œæ˜¾ç¤ºâ€--Return--“。 å¦å¤–, 与之相对应的命令â€r“,表示 step out,å³ç»§ç»æ‰§è¡Œï¼Œç›´åˆ°å½“å‰çš„函数完æˆè¿”回。 命令â€b [ ([filename:]lineno | function) [, condition] ]“å¯ä»¥ç”¨æ¥è®¾ç½®æ–点。比方说,我想è¦åœ¨ä»£ç ä¸çš„第 10 行,å†åŠ 一个æ–点,那么在 pdb 模å¼ä¸‹è¾“å…¥â€b 11“å³å¯ã€‚ 而â€c“则表示一直执行程åºï¼Œç›´åˆ°é‡åˆ°ä¸‹ä¸€ä¸ªæ–点。 当然,除了这些常用命令,还有许多其他的命令å¯ä»¥ä½¿ç”¨ï¼Œè¿™é‡Œæˆ‘å°±ä¸åœ¨ä¸€ä¸€èµ˜è¿°äº†ã€‚ä½ å¯ä»¥å‚考对应的官方文档(https://docs.python.org/3/library/pdb.html#module-pdb),æ¥ç†Ÿæ‚‰è¿™äº›ç”¨æ³•ã€‚ ## 用 cProfile è¿›è¡Œæ€§èƒ½åˆ†æž å…³äºŽè°ƒè¯•çš„å†…å®¹ï¼Œæˆ‘ä¸»è¦å…ˆè®²è¿™ä¹ˆå¤šã€‚事实上,除了è¦å¯¹ç¨‹åºè¿›è¡Œè°ƒè¯•ï¼Œæ€§èƒ½åˆ†æžä¹Ÿæ˜¯æ¯ä¸ªå¼€å‘者的必备技能。 日常工作ä¸ï¼Œæˆ‘们常常会é‡åˆ°è¿™æ ·çš„问题:在线上,我å‘现产å“çš„æŸä¸ªåŠŸèƒ½æ¨¡å—效率低下,延迟(latency)高,å 用的资æºå¤šï¼Œä½†å´ä¸çŸ¥é“是哪里出了问题。 这时,对代ç 进行 profile 就显得异常é‡è¦äº†ã€‚ 这里所谓的 profile,是指对代ç çš„æ¯ä¸ªéƒ¨åˆ†è¿›è¡ŒåŠ¨æ€çš„分æžï¼Œæ¯”如准确计算出æ¯ä¸ªæ¨¡å—消耗的时间ç‰ã€‚ è¿™æ ·ä½ å°±å¯ä»¥çŸ¥é“程åºçš„瓶颈所在,从而对其进行修æ£æˆ–优化。当然,这并ä¸éœ€è¦ä½ 花费特别大的力气,在 Python ä¸ï¼Œè¿™äº›éœ€æ±‚用 cProfile å°±å¯ä»¥å®žçŽ°ã€‚ 举个例å,比如我想计算æ–波拉契数列,è¿ç”¨é€’å½’æ€æƒ³ï¼Œæˆ‘们很容易就能写出下é¢è¿™æ ·çš„代ç : ```python def fib(n): if n == 0: return 0 elif n == 1: return 1 else: return fib(n-1) + fib(n-2) def fib_seq(n): res = [] if n > 0: res.extend(fib_seq(n-1)) res.append(fib(n)) return res fib_seq(30) ``` 接下æ¥ï¼Œæˆ‘想è¦æµ‹è¯•ä¸€ä¸‹è¿™æ®µä»£ç 总的效率以åŠå„个部分的效率。那么,我就åªéœ€åœ¨å¼€å¤´å¯¼å…¥ cProfile 这个模å—,并且在最åŽè¿è¡Œ cProfile.run() å°±å¯ä»¥äº†ï¼š ```python import cProfile def fib(n):... def fib_seq(n):... cProfile.run('fib_seq(30)') ``` 或者更简å•ä¸€äº›ï¼Œç›´æŽ¥åœ¨è¿è¡Œè„šæœ¬çš„命令ä¸ï¼ŒåŠ 入选项“-m cProfileâ€ä¹Ÿå¾ˆæ–¹ä¾¿ï¼š ```shell python3 -m cProfile xxx.py ``` è¿è¡Œå®Œæ¯•åŽï¼Œæˆ‘们å¯ä»¥çœ‹åˆ°ä¸‹é¢è¿™ä¸ªè¾“出界é¢ï¼š  ```shell 7049218 function calls (96 primitive calls) in 2.280 seconds Ordered by: standard name ncalls tottime percall cumtime percall filename:lineno(function) 1 0.000 0.000 2.280 2.280 <string>:1(<module>) 31/1 0.000 0.000 2.280 2.280 demo.py:10(fib_seq) 7049123/31 2.280 0.000 2.280 0.074 demo.py:3(fib) 1 0.000 0.000 2.280 2.280 {built-in method builtins.exec} 31 0.000 0.000 0.000 0.000 {method 'append' of 'list' objects} 1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects} 30 0.000 0.000 0.000 0.000 {method 'extend' of 'list' objects} ``` 这里有一些å‚æ•°ä½ å¯èƒ½æ¯”较陌生,我æ¥ç®€å•ä»‹ç»ä¸€ä¸‹ï¼š + ncalls,是指相应代ç / 函数被调用的次数; + tottime,是指对应代ç / 函数总共执行所需è¦çš„时间(注æ„,并ä¸åŒ…括它调用的其他代ç / 函数的执行时间); + tottime percall,就是上述两者相除的结果,也就是tottime / ncallsï¼› + cumtime,则是指对应代ç / 函数总共执行所需è¦çš„时间,这里包括了它调用的其他代ç / 函数的执行时 + cumtime percall,则是 cumtime å’Œ ncalls 相除的平å‡ç»“果。 了解这些å‚æ•°åŽï¼Œå†æ¥çœ‹è¿™å¼ 图。我们å¯ä»¥æ¸…晰地看到,这段程åºæ‰§è¡Œæ•ˆçŽ‡çš„瓶颈,在于第二行的函数 fib(),它被调用了 700 多万次。 有没有什么办法å¯ä»¥æ高改进呢?ç”案是肯定的。通过观察,我们å‘现,程åºä¸æœ‰å¾ˆå¤šå¯¹ fib() 的调用,其实是é‡å¤çš„,那我们就å¯ä»¥ç”¨å—å…¸æ¥ä¿å˜è®¡ç®—过的结果,防æ¢é‡å¤ã€‚ 改进åŽçš„代ç 如下所示: ```python def memoize(f): memo = {} def helper(x): if x not in memo: memo[x] = f(x) return memo[x] return helper @memoize def fib(n): if n == 0: return 0 elif n == 1: return 1 else: return fib(n-1) + fib(n-2) def fib_seq(n): res = [] if n > 0: res.extend(fib_seq(n-1)) res.append(fib(n)) return res fib_seq(30) ``` 这时,我们å†å¯¹å…¶è¿›è¡Œ profileï¼Œä½ å°±ä¼šå¾—åˆ°æ–°çš„è¾“å‡ºç»“æžœï¼Œå¾ˆæ˜Žæ˜¾ï¼Œæ•ˆçŽ‡å¾—åˆ°äº†æžå¤§çš„æ高。 这个简å•çš„例å,便是 cProfile 的基本用法,也是我今天想讲的é‡ç‚¹ã€‚当然,cProfile 还有很多其他功能,还å¯ä»¥ç»“åˆ stats ç±»æ¥ä½¿ç”¨ï¼Œä½ å¯ä»¥é˜…读相应的 [官方文档](https://docs.python.org/3.7/library/profile.html) æ¥äº†è§£ã€‚ ## 总结 这节课,我们一起å¦ä¹ 了 Python ä¸å¸¸ç”¨çš„调试工具 pdb,和ç»å…¸çš„性能分æžå·¥å…· cProfile。 pdb 为 Python 程åºæ供了一ç§é€šç”¨çš„ã€äº¤äº’å¼çš„高效率调试方案; 而 cProfile 则是为开å‘者æ供了æ¯ä¸ªä»£ç å—执行效率的详细分æžï¼Œæœ‰åŠ©äºŽæˆ‘们对程åºçš„优化与æ高。 å…³äºŽå®ƒä»¬çš„æ›´å¤šç”¨æ³•ï¼Œä½ å¯ä»¥é€šè¿‡å®ƒä»¬çš„官方文档进行实践,都ä¸å¤ªéš¾ï¼Œç†Ÿèƒ½ç”Ÿå·§ã€‚